package com.zc.framework.utils;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;

/**
 * Created by zc on 2016/9/12.
 */
public class FtpHelper {

    private static final Logger LOGGER         = LogManager.getLogger(FtpHelper.class);

    private static final String FILE_UNICODE   = "iso-8859-1";

    private static final String FTP_ENCODING   = "UTF-8";

    private static final String FILE_SEPARATOR = "/";

    public static final int ONE_KILO = 1024;

    public static final int TEN = 10;

    public static final int ONE_HUNDRED = 100;

    public static final int ONE_THOUSAND = 10 * 10 * 10;

    private FTPClient ftpClient      = new FTPClient();

    /**
     * 连接到FTP服务器
     *
     * @param host 主机名
     * @param port 端口
     * @param username 用户名
     * @param password 密码
     * @return 是否连接成功
     * @throws IOException
     */
    public boolean connect(String host, int port, String username, String password)
            throws IOException {
        ftpClient.connect(host, port);
        ftpClient.setControlEncoding(FTP_ENCODING);
        ftpClient.setConnectTimeout(600000);
        //ftpClient.setDefaultTimeout(1000000000);
        ftpClient.setDataTimeout(1000000000);
        //ftpClient.setSoTimeout(1000000000);
        if (isPositive()) {
            if (ftpClient.login(username, password)) {
                LOGGER.info("Connect to server({}:{}) sucessfully!", host, port);
                return true;
            }
            LOGGER.error("Invalid userName or password!");
        }

        LOGGER.error("Connect to server({}:{}) failed!", host, port);
        disconnect();
        return false;
    }

    /**
     *
     * 功能描述: <br>
     * ftp连接是否有效
     *
     * @return 连接是否有效
     */
    public boolean isPositive() {
        return FTPReply.isPositiveCompletion(ftpClient.getReplyCode());
    }

    /**
     *
     * 功能描述: <br>
     * 检查连接是否有效,若无效则关闭连接
     */
    private void checkPositive() throws IOException {
        if (!isPositive()) {
            disconnect();
            LOGGER.error("invalid FTP connection！");
        }
    }

    /**
     * 从FTP服务器上下载文件,支持断点续传，上传百分比汇报
     *
     * @param remote 远程文件路径
     * @param local 本地文件路径
     * @return 上传的状态
     * @throws IOException
     */
    public DownloadStatus download(String remote, String local) throws IOException {
        LOGGER.info("Begin to download file:{}->{}.", remote, local);
        checkPositive();
        // 设置被动模式
        ftpClient.enterLocalPassiveMode();
        ftpClient.enterRemotePassiveMode();
        // 设置以二进制方式传输
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        // DownloadStatus result;

        // 检查远程文件是否存在
        FTPFile[] files = ftpClient.listFiles(new String(remote.getBytes(FTP_ENCODING),
                FILE_UNICODE));
        if (files.length != 1) {
            LOGGER.error("Remote File Noexist!");
            return DownloadStatus.Remote_File_Noexist;
        }

        long lRemoteSize = files[0].getSize();
        File f = new File(local);

        // 本地存在文件，进行断点下载
        if (f.exists()) {
            // 进行断点续传，并记录状态
            return breakpointDownload(remote, lRemoteSize, f);
        }// 本地不存在文件，进行完整下载
        else {
            return completeDownload(remote, lRemoteSize, f);
        }
    }


    public DownloadStatus getFiles(String remote) throws IOException {
        LOGGER.info("Begin to get file:{}->{}.", remote);
        checkPositive();
        // 设置被动模式
        ftpClient.enterLocalActiveMode();//.enterLocalPassiveMode();
        // 设置以二进制方式传输
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        // DownloadStatus result;

        // 检查远程文件是否存在
        FTPFile[] files = ftpClient.listFiles(new String(remote.getBytes(FTP_ENCODING),
                FILE_UNICODE));
        for (FTPFile file : files) {

            System.out.println("remote server ftp file name:"+ file.getName());
            LOGGER.info("log get file name:"+file.getName());
        }

        return null;
    }
    /**
     * 从FTP服务器上下载文件,支持断点续传，上传百分比汇报
     *
     * @param remote 远程文件路径
     * @param local 本地文件路径
     * @return 上传的状态
     * @throws IOException
     */
    public DownloadStatus ftpDownload(String remote, String local) throws IOException {
        LOGGER.info("Begin to download file:{}->{}.", remote, local);
        checkPositive();
        // 设置被动模式
        ftpClient.enterLocalPassiveMode();
        // 设置以二进制方式传输
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        // DownloadStatus result;

        // 检查远程文件是否存在
        FTPFile[] files = ftpClient.listFiles(new String(remote.getBytes(FTP_ENCODING),
                FILE_UNICODE));
        if (files.length != 1) {
            LOGGER.error("Remote File Noexist!");
            return DownloadStatus.Remote_File_Noexist;
        }

        long lRemoteSize = files[0].getSize();
        File f = new File(local);

        // 本地存在文件，进行断点下载
        if (f.exists()) {
            // 进行断点续传，并记录状态
            return breakpointDownload(remote, lRemoteSize, f);
        }// 本地不存在文件，进行完整下载
        else {
            File path = null;
            File paths = null;
            try {
                path = new File(local.substring(0, local.lastIndexOf("/")));
                if (!path.isDirectory()) {
                    path.mkdirs();
                }
                paths = new File(path, local.substring(local.lastIndexOf("/") + 1, local.length()));
                paths.createNewFile();
            } catch (Exception e) {
                LOGGER.error("生成本地目录出错！");
            }
            return completeDownload(remote, lRemoteSize, paths);
        }
    }

    /**
     *
     * 功能描述: <br>
     * 断点续传下载
     *
     * @param remote 远程文件目录
     * @param lRemoteSize 文件大小
     * @param f 文件对象
     * @throws FileNotFoundException
     * @throws IOException
     * @throws UnsupportedEncodingException
     */
    private DownloadStatus breakpointDownload(String remote, long lRemoteSize, File f)
            throws IOException {
        long localSize = f.length();
        // 判断本地文件大小是否大于远程文件大小
        if (localSize >= lRemoteSize) {
            LOGGER.error("Local file is bigger then remote's, stop downloading!");
            return DownloadStatus.Local_Bigger_Remote;
        }

        FileOutputStream out = null;
        InputStream in = null;

        try {
            out = new FileOutputStream(f, true);
            ftpClient.setRestartOffset(localSize);
            in = ftpClient.retrieveFileStream(new String(remote.getBytes(FTP_ENCODING),
                    FILE_UNICODE));
            byte[] bytes = new byte[ONE_KILO];
            long step = lRemoteSize / ONE_HUNDRED;
            long process = localSize / step;
            int c;
            while ((c = in.read(bytes)) != -1) {
                out.write(bytes, 0, c);
                localSize += c;
                long nowProcess = localSize / step;
                if (nowProcess > process) {
                    process = nowProcess;
                    if (process % TEN == 0) {
                        LOGGER.info("download processing：" + process);
                    }
                }
            }
            if (ftpClient.completePendingCommand()) {
                LOGGER.info("FTP Download file sucessfully!");
                return DownloadStatus.Download_From_Break_Success;
            }

            LOGGER.error("FTP Download file failed!");
            return DownloadStatus.Download_From_Break_Failed;
        } catch (IOException ex) {
            throw ex;
        } finally {
            closeResources(out, in);
        }
    }

    /**
     *
     * 功能描述: <br>
     * 完整文件下载
     *
     * @param remote 远程文件目录
     * @param lRemoteSize 文件大小
     * @param f 文件对象
     * @throws FileNotFoundException
     * @throws IOException
     * @throws UnsupportedEncodingException
     */
    private DownloadStatus completeDownload(String remote, long lRemoteSize, File f)
            throws IOException {
        OutputStream out = null;
        InputStream in = null;

        try {
            out = new FileOutputStream(f);
            in = ftpClient.retrieveFileStream(new String(remote.getBytes(FTP_ENCODING),
                    FILE_UNICODE));
            byte[] bytes = new byte[ONE_KILO];
            long step = lRemoteSize / ONE_HUNDRED;
            long process = 0;
            long localSize = 0L;
            int c;
            while ((c = in.read(bytes)) != -1) {
                out.write(bytes, 0, c);
                localSize += c;
                long nowProcess = localSize / step;
                if (nowProcess > process) {
                    process = nowProcess;
                    if (process % TEN == 0) {
                        LOGGER.info("download processing：" + process);
                    }
                }
            }

            if (ftpClient.completePendingCommand()) {
                LOGGER.info("FTP Download file sucessfully!");
                return DownloadStatus.Download_New_Success;
            }

            LOGGER.error("FTP Download file failed!");
            return DownloadStatus.Download_New_Failed;
        } catch (IOException ex) {
            throw ex;
        } finally {
            closeResources(out, in);
        }
    }

    /**
     *
     * 功能描述: <br>
     * 关闭资源连接
     *
     * @param out 输出流
     * @param in 输入流
     * @throws IOException
     */
    private void closeResources(OutputStream out, InputStream in) {
        if (in != null) {
            try {
                in.close();
            } catch (IOException ex) {
                LOGGER.error("error in closing inputStream", ex);
            }
        }

        if (out != null) {
            try {
                out.close();
            } catch (IOException ex) {
                LOGGER.error("error in closing outputStream", ex);
            }
        }
    }

    /**
     * 上传文件到FTP服务器，支持断点续传
     *
     * @param local 本地文件名称，绝对路径
     * @param remote 远程文件路径，使用/home/directory1/subdirectory/file.ext
     *            按照Linux上的路径指定方式，支持多级目录嵌套，支持递归创建不存在的目录结构
     * @return 上传结果
     * @throws IOException
     */
    public UploadStatus upload(String local, String remote) throws IOException {
        LOGGER.info("Begin to upload file:{}->{}.", remote, local);
        checkPositive();
        // 设置PassiveMode传输
        ftpClient.enterLocalPassiveMode();
        // 设置以二进制流的方式传输
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        ftpClient.setControlEncoding(FTP_ENCODING);
        UploadStatus result;
        // 对远程目录的处理
        String remoteFileName = remote;
        if (remote.contains(FILE_SEPARATOR)) {
            remoteFileName = remote.substring(remote.lastIndexOf(FILE_SEPARATOR) + 1);
            // 创建服务器远程目录结构，创建失败直接返回
            if (createDirecroty(remote, ftpClient) == UploadStatus.Create_Directory_Fail) {
                return UploadStatus.Create_Directory_Fail;
            }
        }

        // 检查远程是否存在文件
        FTPFile[] files = ftpClient.listFiles(new String(remoteFileName.getBytes(FTP_ENCODING),
                FILE_UNICODE));
        for(FTPFile ftp:files)
        {
            System.out.println(ftp.getName()+","+ftp.isFile());
        }
        if (files.length == 1) {
            long remoteSize = files[0].getSize();
            File f = new File(local);
            long localSize = f.length();
            // 文件存在
            if (remoteSize == localSize) {
                LOGGER.error("Upload file failed, file is exist!");
                return UploadStatus.File_Exits;
            } else if (remoteSize > localSize) {
                LOGGER.error("Upload file failed, file is less than remote's!");
                return UploadStatus.Remote_Bigger_Local;
            }

            // 尝试移动文件内读取指针,实现断点续传
            result = uploadFile(remoteFileName, f, ftpClient, remoteSize);

            // 如果断点续传没有成功，则删除服务器上文件，重新上传
            if (result == UploadStatus.Upload_From_Break_Failed) {
                if (!ftpClient.deleteFile(remoteFileName)) {
                    return UploadStatus.Delete_Remote_Faild;
                }
                result = uploadFile(remoteFileName, f, ftpClient, 0);
            }
        } else {
            result = uploadFile(remoteFileName, new File(local), ftpClient, 0);
        }
        return result;
    }

    /**
     * 上传文件到FTP服务器，支持断点续传
     *
     * @param localDir 本地文件名称，绝对路径
     * @param remoteDir 远程文件路径，使用/home/directory1/subdirectory/file.ext
     *            按照Linux上的路径指定方式，支持多级目录嵌套，支持递归创建不存在的目录结构
     * @return 上传结果
     * @throws IOException
     */
    public UploadStatus uploadDir(String localDir, String remoteDir) throws IOException {
        LOGGER.info("Begin to upload dir:{}->{}.", remoteDir, localDir);
        checkPositive();
        // 设置PassiveMode传输
        ftpClient.enterLocalPassiveMode();
        // 设置以二进制流的方式传输
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
        ftpClient.setControlEncoding(FTP_ENCODING);
        // 对远程目录的处理
        // 创建服务器远程目录结构，创建失败直接返回
        if (createDirecroty(remoteDir.endsWith(FILE_SEPARATOR) ? remoteDir : remoteDir
                + FILE_SEPARATOR, ftpClient) == UploadStatus.Create_Directory_Fail) {
            return UploadStatus.Create_Directory_Fail;
        }

        File lcDir = new File(localDir);
        return FileNested(lcDir);
    }

    private UploadStatus FileNested(File lcDir) throws IOException {
        UploadStatus result = null;
        if (lcDir.isDirectory()) {
            ftpClient.makeDirectory(lcDir.getName());
            ftpClient.changeWorkingDirectory(lcDir.getName());

            String[] localFiles = lcDir.list();

            for (String local : localFiles) {
                File subFile = new File(lcDir.getPath()+FILE_SEPARATOR+local);
                if (subFile.isDirectory()) {
                    FileNested(subFile);
                    ftpClient.changeToParentDirectory();
                } else {
                    FTPFile[] files = ftpClient.listFiles(new String(local.getBytes(FTP_ENCODING),
                            FILE_UNICODE));
                    if (files.length == 1) {
                        long remoteSize = files[0].getSize();
                        File f = new File(lcDir.getPath()+FILE_SEPARATOR+local);
                        long localSize = f.length();
                        // 文件存在
                        if (remoteSize == localSize) {
                            LOGGER.error("Upload file failed, file is exist!");
                            //return UploadStatus.File_Exits;
                            continue;
                        } else if (remoteSize > localSize) {
                            LOGGER.error("Upload file failed, file is less than remote's!");
                            //return UploadStatus.Remote_Bigger_Local;
                            continue;
                        }

                        // 尝试移动文件内读取指针,实现断点续传
                        result = uploadFile(local, f, ftpClient, remoteSize);

                        // 如果断点续传没有成功，则删除服务器上文件，重新上传
                        if (result == UploadStatus.Upload_From_Break_Failed) {
                            if (!ftpClient.deleteFile(local)) {
                                return UploadStatus.Delete_Remote_Faild;
                            }
                            result = uploadFile(local, f, ftpClient, 0);
                        }
                    } else {
                        result = uploadFile(local, new File(lcDir.getPath()+FILE_SEPARATOR+local), ftpClient, 0);
                    }
                }
            }
        } else {
            result = uploadFile(lcDir.getPath(), lcDir, ftpClient, 0);
        }
        return result;
    }

    /**
     * 断开与远程服务器的连接
     *
     * @throws IOException
     */
    public void disconnect() throws IOException {
        if (ftpClient.isConnected()) {
            ftpClient.logout();
            ftpClient.disconnect();
            LOGGER.info("Disconnect ftp server sucessfully!");
        }
    }

    /**
     * 递归创建远程服务器目录
     *
     * @param remote 远程服务器文件绝对路径
     * @param ftpClient FTPClient对象
     * @return 目录创建是否成功
     * @throws IOException
     */
    public UploadStatus createDirecroty(String remote, FTPClient ftpClient) throws IOException {
        UploadStatus status = UploadStatus.Create_Directory_Success;
        String directory = remote.substring(0, remote.lastIndexOf(FILE_SEPARATOR) + 1);
        if (!directory.equalsIgnoreCase(FILE_SEPARATOR)
                && !ftpClient.changeWorkingDirectory(new String(directory.getBytes(FTP_ENCODING),
                FILE_UNICODE))) {
            // 如果远程目录不存在，则递归创建远程服务器目录
            int start = 0;
            int end = 0;
            if (directory.startsWith(FILE_SEPARATOR)) {
                start = 1;
            } else {
                start = 0;
            }
            end = directory.indexOf(FILE_SEPARATOR, start);
            while (true) {
                String subDirectory = new String(remote.substring(start, end)
                        .getBytes(FTP_ENCODING), FILE_UNICODE);
                if (!ftpClient.changeWorkingDirectory(subDirectory)) {
                    if (ftpClient.makeDirectory(subDirectory)) {
                        ftpClient.changeWorkingDirectory(subDirectory);
                    } else {
                        LOGGER.error("create folder failed.");
                        return UploadStatus.Create_Directory_Fail;
                    }
                }

                start = end + 1;
                end = directory.indexOf(FILE_SEPARATOR, start);

                // 检查所有目录是否创建完毕
                if (end <= start) {
                    break;
                }
            }
        }
        return status;
    }

    /**
     * 上传文件到服务器,新上传和断点续传
     *
     * @param remoteFile 远程文件名，在上传之前已经将服务器工作目录做了改变
     * @param localFile 本地文件File句柄，绝对路径
     * @param remoteSize 需要显示的处理进度步进值
     * @param ftpClient FTPClient引用
     * @return
     * @throws IOException
     */
    public UploadStatus uploadFile(String remoteFile, File localFile, FTPClient ftpClient,
                                   long remoteSize) throws IOException {
        UploadStatus status;
        // 显示进度的上传
        long step = (localFile.length() < ONE_HUNDRED) ? 1
                : (localFile.length() / ONE_HUNDRED);
        long process = 0;
        long localreadbytes = 0L;
        RandomAccessFile raf = new RandomAccessFile(localFile, "r");
        OutputStream out = ftpClient.appendFileStream(new String(remoteFile.getBytes(FTP_ENCODING),
                FILE_UNICODE));
        // 断点续传
        if (remoteSize > 0) {
            ftpClient.setRestartOffset(remoteSize);
            process = remoteSize / step;
            raf.seek(remoteSize);
            localreadbytes = remoteSize;
        }
        byte[] bytes = new byte[ONE_KILO];
        int c;
        while ((c = raf.read(bytes)) != -1) {
            out.write(bytes, 0, c);
            localreadbytes += c;
            if (localreadbytes / step != process) {
                process = localreadbytes / step;
                LOGGER.info("upload processing:" + process);

            }
        }
        out.flush();
        raf.close();
        out.close();
        boolean result = ftpClient.completePendingCommand();
        if (remoteSize > 0) {
            status = result ? UploadStatus.Upload_From_Break_Success
                    : UploadStatus.Upload_From_Break_Failed;
        } else {
            status = result ? UploadStatus.Upload_New_File_Success
                    : UploadStatus.Upload_New_File_Failed;
        }

        if (status.equals(UploadStatus.Upload_New_File_Success)
                || status.equals(UploadStatus.Upload_From_Break_Success)) {
            LOGGER.info("Upload file sucessfully!");
        }

        return status;
    }

    /**
     *
     * 功能描述: <br>
     * 递归删除某个文件夹下的文件; 根据传入的状态判断是否同时删除当前文件夹
     *
     * @param directory 删除文件的目录
     * @param delFolder 是否同时删除该文件夹
     * @throws IOException
     */
    public DeleteSatsus delRemoteDirectory(String directory, boolean delFolder) throws IOException {
        String strDirectory = directory.endsWith(FILE_SEPARATOR) ? directory : directory
                + FILE_SEPARATOR;

        FTPFile[] files = listFiles(strDirectory);
        for (FTPFile file : files) {
            if (!file.isDirectory()) {
                ftpClient.deleteFile(strDirectory + file.getName());
                continue;
            }

            if (file.isDirectory()) {
                delRemoteDirectory(strDirectory + file.getName(), true);
            }
        }

        if (delFolder) {
            if (!removeDirectory(strDirectory)) {
                LOGGER.error("Delete remote directory failed!");
                return DeleteSatsus.Remove_Directory_Fail;
            }
        } else {
            files = listFiles(strDirectory);
            if (files != null && files.length != 0) {
                LOGGER.error("Delete remote directory failed!");
                return DeleteSatsus.Remove_Directory_Fail;
            }
        }

        LOGGER.info("Delete remote directory sucessfully!");
        return DeleteSatsus.Remove_Directory_Success;
    }

    /**
     *
     * 功能描述: <br>
     * 列表显示某路径下的文件
     *
     * @param directory 文件路径
     * @return 所有文件
     * @throws IOException
     */
    public FTPFile[] listFiles(String directory) throws IOException {
        return ftpClient.listFiles(directory);
    }

    /**
     *
     * 功能描述: <br>
     * 删除单个文件.
     *
     * @param filePath 文件路径
     * @return DeleteSatsus
     * @throws IOException
     */
    public DeleteSatsus delRemoteFile(String filePath) throws IOException {
        return deleteFile(filePath) ? DeleteSatsus.Delete_Remote_File_Success
                : DeleteSatsus.Delete_Remote_File_Faild;
    }

    /**
     *
     * 功能描述: <br>
     * 删除单个文件.
     *
     * @param filePath 文件路径
     * @return 删除是否成功状态
     * @throws IOException
     */
    public boolean deleteFile(String filePath) throws IOException {
        return ftpClient.deleteFile(filePath);
    }

    /**
     *
     * 功能描述: <br>
     * 删除单个文件目录,该目录必须为空.
     *
     * @param directory 目录路径
     * @return 删除是否成功状态
     * @throws IOException
     */
    public boolean removeDirectory(String directory) throws IOException {
        return ftpClient.removeDirectory(directory);
    }

    /**
     *
     * 功能描述: <br>
     * 获取此次打开的FTPClient实例.
     *
     * @return 此次打开的FTPClient实例
     */
    public FTPClient getFtpClient() {
        return ftpClient;
    }


    public enum UploadStatus {
        Create_Directory_Fail, // 远程服务器相应目录创建失败
        Create_Directory_Success, // 远程服务器闯将目录成功
        Upload_New_File_Success, // 上传新文件成功
        Upload_New_File_Failed, // 上传新文件失败
        File_Exits, // 文件已经存在
        Remote_Bigger_Local, // 远程文件大于本地文件
        Upload_From_Break_Success, // 断点续传成功
        Upload_From_Break_Failed, // 断点续传失败
        Delete_Remote_Faild, UploadStatus, // 删除远程文件失败
    }


    public enum DownloadStatus {
        Remote_File_Noexist, // 远程文件不存在
        Download_New_Success, // 下载文件成功
        Download_New_Failed, // 下载文件失败
        Local_Bigger_Remote, // 本地文件大于远程文件
        Download_From_Break_Success, // 断点续传成功
        Download_From_Break_Failed; // 断点续传失败
    }


    public enum DeleteSatsus {
        Delete_Remote_File_Faild, // 删除远程文件失败
        Delete_Remote_File_Success, // 删除远程文件成功
        Remove_Directory_Noexist, // 远程目录不存在
        Remove_Directory_Success, // 删除远程目录成功
        Remove_Directory_Fail,
        DeleteSatsus; // 删除远程目录成功
    }
}
